3.2.2 浅拷贝和深拷贝

JS 中的浅拷贝与深拷贝,只是针对复杂数据类型(Object,Array)的复制问题。

浅拷贝与深拷贝都可以实现在已有对象上再生出一份的作用。但是对象的实例是存储

在堆内存中然后通过一个引用值去操作对象,由此拷贝的时候就存在两种情况了:拷贝引用和拷贝实例,这也是浅拷贝和深拷贝的区别。

  • 浅拷贝:浅拷贝是拷贝引用,拷贝后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响

  • 深拷贝:在堆中重新分配内存,并且把源对象所有属性都进行新建拷贝, 以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,拷贝后的对象与原 来的对象是完全隔离,互不影响

  • 浅拷贝

浅拷贝分两种情况,拷贝直接拷贝源对象的引用 和 源对象拷贝实例,但其属性(类型为Object,Array的属性)拷贝引用。

拷贝原对象的引用

这是最简单的浅拷贝。例:

var a = {c:1};
var b = a;
console.log(a === b); // 输出true。
a.c = 2;
console.log(b.c); // 输出 2
1
2
3
4
5

源对象拷贝实例,其属性对象拷贝引用。

这种情况,外层源对象是拷贝实例,如果其属性元素为复杂杂数据类型时,内层元素拷贝引用。 对源对象直接操作,不影响两外一个对象,但是对其属性操作时候,会改变两外一个对象的属性的只。 常用方法为:Array.prototype.slice(), Array.prototype.concat(), jQury的$.extend({},obj),例:

var a = [{c:1}, {d:2}];
var b = a.slice();
console.log(a === b); // 输出false,说明外层数组拷贝的是实例
a[0].c = 3;
console.log(b[0].c); // 输出 3,说明其元素拷贝的是引用
1
2
3
4
5
  • 深拷贝

深拷贝后,两个对象,包括其内部的元素互不干扰。
第一种用JSON.parse(JSON.stringify(obj)),
第二种可以使用for...in加递归完成

递归

function isObj(obj) {
    return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function deepCopy(obj) {
    let tempObj = Array.isArray(obj) ? [] : {}
    for(let key in obj) {
        tempObj[key] = isObj(obj[key]) ? deepCopy(obj[key]) : obj[key]
    }
    return tempObj
}

作者:YDJFE
链接:https://juejin.im/post/5b235b726fb9a00e8a3e4e88
1
2
3
4
5
6
7
8
9
10
11
12
13

常见方法有JSON.parse(),JSON.stringify(),jQury的$.extend(true,{},obj),lodash的_.cloneDeep和_.clone(value, true)。例:

var a = {c: {d: 1}};
var b = $.extend(true, {}, a);
console.log(a === b); // 输出false
a.c.d = 3;
console.log(b.c.d); // 输出 1,没有改变。


// JSON.parse(),JSON.stringify()
const oldObj = {
  a: 1,
  b: [ 'e', 'f', 'g' ],
  c: { h: { i: 2 } }
};

const newObj = JSON.parse(JSON.stringify(oldObj));
console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 }
console.log(oldObj.c.h === newObj.c.h); // false
newObj.c.h.i = 'change';
console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 2 }

作者:寻找海蓝96
链接:https://juejin.im/post/5abb55ee6fb9a028e33b7e0a

确实,这个方法虽然可以解决绝大部分是使用场景,但是却有很多坑.

1.他无法实现对函数 、RegExp等特殊对象的克隆

2.会抛弃对象的constructor,所有的构造函数会指向Object

3.对象有循环引用,会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

原始类型对象的克隆

1.字符串的克隆
var x="1";
var y=x;
y="2";
console.log(x) //"1"  源不变
console.log(y) //"2"

2.数值的克隆
var x=1;
var y=x;
y=2;
console.log(x) //1  源不变
console.log(y) //2

3.布尔值的克隆
var x=true;
var y=x;
y=false;
console.log(x) //true  源不变
console.log(y) //false

4.数组的克隆
var x=[1,2];
var y=x;
y[3] = 8;

console.log(x) //[1, 2, empty, 8]  源跟着变化
console.log(y) //[1, 2, empty, 8]
原始数组x,克隆数组y,修改了克隆数组y,但也同时修改了原始数组x,这就是引用对象的特点。
=====================================
var x=[1,2];
var y=[];
var i=0;
var j=x.length;
for(;i<j;i++)
{
	y[i]=x[i];
}
y[3] = 8;

console.log(x) //[1, 2]  源不变
console.log(y) //[1, 2, empty, 8]
克隆数组y,原始数组x,两个数组互补干扰,实现了完整的数组克隆
--------------------------------------
var _test = [1,2,3];//原数组
var _testCopy = [].concat(_test);//拷贝数组
_testCopy[0]=4;
console.log(_test);// [1,2,3]
console.log(_testCopy);//[4,2,3]
-----------------------------------


5. 对象的克隆
和数组的克隆同理,
var x={1:2,3:4};
var y=x;
y[4] =6
console.log(x) //{1: 2, 3: 4, 4: 6}  源变
console.log(y) //{1: 2, 3: 4, 4: 6}

======================================
完整的克隆
var x={1:2,3:4};
var y={};
var i;
for(i in x)
{
  y[i]=x[i];
}
y[5]=6;
 
console.log(x); // Object {1: 2, 3: 4} 
console.log(y);// Object {1: 2, 3: 4, 5: 6} 

------------------------------------------
var _test = [{"name":"weifeng"},{"name":"boy"}];//原数组
var _testCopy = [].concat(JSON.parse(JSON.stringify(_test)));//拷贝数组,注意这行的拷贝方法
_testCopy[1].name="girl";
console.log(_test);// [{"name":"weifeng"},{"name":"boy"}]
console.log(_testCopy);//[{"name":"weifeng"},{"name":"girl"}]
------------------------------------------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

完整的对象克隆demo,在生产环境中最好用lodash的深克隆实现.

function clone(obj)
{
	var o,i,j,k;
	if(typeof(obj)!="object" || obj===null)return obj;
	if(obj instanceof(Array))
	{
		o=[];
		i=0;j=obj.length;
		for(;i<j;i++)
		{
			if(typeof(obj[i])=="object" && obj[i]!=null)
			{
				o[i]=arguments.callee(obj[i]);
			}
			else
			{
				o[i]=obj[i];
			}
		}
	}
	else
	{
		o={};
		for(i in obj)
		{
			if(typeof(obj[i])=="object" && obj[i]!=null)
			{
				o[i]=arguments.callee(obj[i]);
			}
			else
			{
				o[i]=obj[i];
			}
		}
	}
	return o;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

// 只解决date,reg类型,其他的可以自己添加

function deepCopy(obj, hash = new WeakMap()) {
    let cloneObj
    let Constructor = obj.constructor
    switch(Constructor){
        case RegExp:
            cloneObj = new Constructor(obj)
            break
        case Date:
            cloneObj = new Constructor(obj.getTime())
            break
        default:
            if(hash.has(obj)) return hash.get(obj)
            cloneObj = new Constructor()
            hash.set(obj, cloneObj)
    }
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }
    return cloneObj
}


作者:YDJFE
链接:https://juejin.im/post/5b235b726fb9a00e8a3e4e88
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

递归去复制所有层级属性。

深拷贝的函数

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    
let a=[1,2,3,4],
    b=deepClone(a);
a[0]=2;
console.log(a,b);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

生产环境还是使用 loadsh 中封装好的

参考